﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Noris.Tools.TraceDownloader.Downloader
{
    #region MoveMode : režimy závěrečného kopírování
    /// <summary>
    /// MoveMode : režimy závěrečného kopírování
    /// </summary>
    internal enum MoveMode
    {
        None,
        FileCopy,
        FileMove
    }
    #endregion
    #region MoveInfo : Třída popisující režimy kopírování výsledků. Abstraktní předek pro konkrétní implementace kopírování.
    /// <summary>
    /// MoveInfo : Třída popisující režimy kopírování výsledků.
    /// Abstraktní předek pro konkrétní implementace kopírování.
    /// </summary>
    internal abstract class MoveInfo
    {
        #region Konstrukce a základní datové property
        /// <summary>
        /// Konstruktor je privátní, objekty se generují statickou metodou Create() nebo přes property ModeList.
        /// </summary>
        /// <param name="moveMode"></param>
        /// <param name="title"></param>
        /// <param name="toolTip"></param>
        protected MoveInfo(MoveMode moveMode, string title, string toolTip, bool needOutDir, float relativeTime)
        {
            this.MoveMode = moveMode;
            this.Title = title;
            this.ToolTip = toolTip;
            this.NeedOutDir = needOutDir;
            this.RelativeTime = relativeTime;
        }
        public override string ToString()
        {
            return this.GetType().Name + "; " + this.Title;
        }
        /// <summary>
        /// Obsahuje (vždy vytvoří nový) soupis režimů a jejich vizuální vlastnosti
        /// </summary>
        internal static IEnumerable<MoveInfo> ModeList
        {
            get
            {
                List<MoveInfo> result = new List<MoveInfo>();
                result.Add(Create(MoveMode.None));
                result.Add(Create(MoveMode.FileCopy));
                result.Add(Create(MoveMode.FileMove));
                return result;
            }
        }
        /// <summary>
        /// Vrátí objekt daného typu (class factory)
        /// </summary>
        /// <param name="moveMode"></param>
        /// <returns></returns>
        internal static MoveInfo Create(MoveMode moveMode)
        {
            if (MoveNoneInfo.ImplementMoveMode(moveMode)) return MoveNoneInfo.Create(moveMode);
            if (MoveFileInfo.ImplementMoveMode(moveMode)) return MoveFileInfo.Create(moveMode);
            throw new InvalidOperationException("Třída MoveInfo nemůže vygenerovat objekt pro režim MoveMode = " + moveMode.ToString() + ", nezná třídu která by jej implementovala.");
        }
        /// <summary>
        /// Konkrétní režim
        /// </summary>
        public MoveMode MoveMode { get; protected set; }
        /// <summary>
        /// Text popisující předvolbu (do GUI)
        /// </summary>
        public string Title { get; protected set; }
        /// <summary>
        /// Tooltip k položce
        /// </summary>
        public string ToolTip { get; protected set; }
        /// <summary>
        /// Potřebuje výstupní adresář
        /// </summary>
        public bool NeedOutDir{ get; protected set; }
        /// <summary>
        /// Relativní náročnost z celkového progresu
        /// </summary>
        public float RelativeTime { get; protected set; }
        #endregion
        #region Proces zipování: back thread, abstract metoda Pack()
        /// <summary>
        /// Zahájí proces závěrečného zpracování a ihned vrátí řízení.
        /// S volající stranou komunikuje prostřednictvím handleru 
        /// </summary>
        /// <param name="files"></param>
        /// <param name="outDirectory"></param>
        /// <param name="callbackHandler"></param>
        internal void Run(IEnumerable<System.IO.FileInfo> files, string outDirectory, AsyncWorkCallbackHandler callbackHandler)
        {
            this.FilesToMove = files;
            this.OutDirectory = outDirectory;
            this.CallbackHandler = callbackHandler;

            this.WorkingThread = new System.Threading.Thread(this._RunThread);
            this.WorkingThread.IsBackground = true;
            this.WorkingThread.Name = "move_final";
            this.WorkingThread.Priority = System.Threading.ThreadPriority.Lowest;
            this.WorkingThread.TrySetApartmentState(System.Threading.ApartmentState.MTA);

            this.WorkingThread.Start();
        }
        protected virtual void _RunThread()
        {
            try
            {
                this.CallStart();
                this.ResultFiles = this.Move();
                this.CallSuccess();
            }
            catch (Exception exc)
            {
                this.CallError(exc);
            }
        }
        public virtual void Abort()
        {
            try
            {
                if (this.WorkingThread != null)
                {
                    if (this.WorkingThread.IsAlive)
                    {
                    }
                    this.WorkingThread.Abort();
                }
            }
            catch (Exception exc)
            { }
        }
        protected System.Threading.Thread WorkingThread;
        protected IEnumerable<System.IO.FileInfo> FilesToMove;
        /// <summary>
        /// Soupis výsledných souborů
        /// </summary>
        public IEnumerable<System.IO.FileInfo> ResultFiles { get; protected set; }
        /// <summary>
        /// Název souboru jak by jej chtěla mít volající rutina (= bez přípony)
        /// </summary>
        protected string OutDirectory;
        /// <summary>
        /// Stav procesu
        /// </summary>
        public AsyncWorkState WorkState { get; protected set; }
        /// <summary>
        /// Výkonná metoda, na třídě MoveInfo je abstraktní, výkon provádí konkrétní potomek (podle typu kopírování)
        /// </summary>
        protected abstract IEnumerable<System.IO.FileInfo> Move();
        #endregion
        #region Komunikace s volající stranou (Callback)
        private void CallStart()
        {
            this.WorkState = AsyncWorkState.Start;
            if (this.CallbackHandler != null)
                this.CallbackHandler(this, AsyncWorkCallbackArgs.Start());
        }
        protected bool CallProgress(decimal progressRatio)
        {
            this.WorkState = AsyncWorkState.Progress;
            AsyncWorkCallbackArgs args = AsyncWorkCallbackArgs.Progress(progressRatio);
            if (this.CallbackHandler != null)
                this.CallbackHandler(this, args);
            return args.Cancel;
        }
        protected bool CallProgress(string progressMessage)
        {
            this.WorkState = AsyncWorkState.Progress;
            AsyncWorkCallbackArgs args = AsyncWorkCallbackArgs.Progress(progressMessage);
            if (this.CallbackHandler != null)
                this.CallbackHandler(this, args);
            return args.Cancel;
        }
        protected bool CallProgress(decimal progressRatio, string progressMessage)
        {
            this.WorkState = AsyncWorkState.Progress;
            AsyncWorkCallbackArgs args = AsyncWorkCallbackArgs.Progress(progressRatio, progressMessage);
            if (this.CallbackHandler != null)
                this.CallbackHandler(this, args);
            return args.Cancel;
        }
        private void CallSuccess()
        {
            this.WorkState = AsyncWorkState.Success;
            if (this.CallbackHandler != null)
                this.CallbackHandler(this, AsyncWorkCallbackArgs.Success());
        }
        protected void CallError(Exception exc)
        {
            this.WorkState = AsyncWorkState.Error;
            if (this.CallbackHandler != null)
                this.CallbackHandler(this, AsyncWorkCallbackArgs.Error(exc));
        }
        protected AsyncWorkCallbackHandler CallbackHandler;
        #endregion
    }
    #endregion
    #region MoveNoneInfo : void provider, který nic nedělá, ale úhledně
    internal class MoveNoneInfo : MoveInfo
    {
        /// <summary>
        /// Konstruktor je privátní, objekty se generují statickou metodou Create() nebo přes property ModeList.
        /// </summary>
        /// <param name="moveMode"></param>
        /// <param name="title"></param>
        /// <param name="toolTip"></param>
        protected MoveNoneInfo(MoveMode moveMode, string title, string toolTip, bool needOutDir, float relativeTime)
            : base(moveMode, title, toolTip, needOutDir, relativeTime)
        { }
        /// <summary>
        /// Vrátí true, pokud tato třída implementuje daný režim
        /// </summary>
        /// <param name="packMode"></param>
        /// <returns></returns>
        internal static bool ImplementMoveMode(MoveMode moveMode)
        {
            return (moveMode == MoveMode.None);
        }
        /// <summary>
        /// Vrátí objekt MoveInfo pro daný typ kopírování
        /// </summary>
        /// <param name="moveMode"></param>
        /// <returns></returns>
        internal static new MoveInfo Create(MoveMode moveMode)
        {
            switch (moveMode)
            {
                case MoveMode.None: return new MoveNoneInfo(MoveMode.None, "Nic dalšího", "Po dokončení zipování se nic dalšího neprovádí.", false, 0f);
            }
            throw new InvalidOperationException("Třída MoveNoneInfo nemůže vygenerovat objekt pro režim MoveMode = " + moveMode.ToString());
        }
        protected override IEnumerable<System.IO.FileInfo> Move()
        {
            // Základní vlastností tohoto řešení je, že nic nedělá.
            return new List<System.IO.FileInfo>();
        }
    }
    #endregion
    #region MoveFileInfo : provider, který kopíruje / přesouvá soubory běžným IO režimem
    internal class MoveFileInfo : MoveInfo
    {
        /// <summary>
        /// Konstruktor je privátní, objekty se generují statickou metodou Create() nebo přes property ModeList.
        /// </summary>
        /// <param name="moveMode"></param>
        /// <param name="title"></param>
        /// <param name="toolTip"></param>
        protected MoveFileInfo(MoveMode moveMode, string title, string toolTip, bool needOutDir, float relativeTime)
            : base(moveMode, title, toolTip, needOutDir, relativeTime)
        { }
        /// <summary>
        /// Vrátí true, pokud tato třída implementuje daný režim
        /// </summary>
        /// <param name="packMode"></param>
        /// <returns></returns>
        internal static bool ImplementMoveMode(MoveMode moveMode)
        {
            return (moveMode == MoveMode.FileCopy || moveMode == MoveMode.FileMove);
        }
        /// <summary>
        /// Vrátí objekt MoveInfo pro daný typ kopírování
        /// </summary>
        /// <param name="moveMode"></param>
        /// <returns></returns>
        internal static new MoveInfo Create(MoveMode moveMode)
        {
            switch (moveMode)
            {
                case MoveMode.FileCopy: return new MoveFileInfo(MoveMode.FileCopy, "Zkopírovat výsledky do adresáře", "Po dokončení zipování se vytvořené soubory zkopírují do uvedeného adresáře.", true, 0.15f);
                case MoveMode.FileMove: return new MoveFileInfo(MoveMode.FileMove, "Přesunout výsledky do adresáře", "Po dokončení zipování se vytvořené soubory přesunou do uvedeného adresáře.", true, 0.15f);
            }
            throw new InvalidOperationException("Třída MoveFileInfo nemůže vygenerovat objekt pro režim MoveMode = " + moveMode.ToString());
        }
        protected override IEnumerable<System.IO.FileInfo> Move()
        {
            List<System.IO.FileInfo> resultFiles = new List<System.IO.FileInfo>();
            long totalLengthBytes = this.FilesToMove.Sum(fi => fi.Length);
            long totalMovedBytes = 0;
            foreach (System.IO.FileInfo file in this.FilesToMove)
            {
                System.IO.FileInfo result = this.MoveOne(file, totalLengthBytes, ref totalMovedBytes);
                if (result != null)
                    resultFiles.Add(result);
            }
            return resultFiles;
        }
        private System.IO.FileInfo MoveOne(System.IO.FileInfo inputFile, long totalLengthBytes, ref long totalMovedBytes)
        {
            System.IO.FileInfo result = null;
            if (!inputFile.Exists) return result;

            if (!System.IO.Directory.Exists(this.OutDirectory))
                System.IO.Directory.CreateDirectory(this.OutDirectory);

            string targetFileName = System.IO.Path.Combine(this.OutDirectory, inputFile.Name);
            long fileLengthBytes = inputFile.Length;
            long fileMovedBytes = 0;
            try
            {
                using (System.IO.FileStream inputStream = new System.IO.FileStream(inputFile.FullName, System.IO.FileMode.Open, System.IO.FileAccess.Read))
                using (System.IO.FileStream outputStream = new System.IO.FileStream(targetFileName, System.IO.FileMode.Create, System.IO.FileAccess.Write))
                {
                    int bufferLength = 0x10000;                // 65KB buffer
                    byte[] buffer = new byte[bufferLength];

                    while (true)
                    {
                        int readBytes = inputStream.Read(buffer, 0, bufferLength);
                        if (readBytes == 0)
                            break;

                        outputStream.Write(buffer, 0, readBytes);
                        fileMovedBytes += readBytes;

                        long totalCurrentBytes = totalMovedBytes + fileMovedBytes;
                        decimal progressRatio = (decimal)(totalCurrentBytes) / (decimal)totalLengthBytes;
                        string progressMessage = "Copy: " + Preset.FormatProcessedByte(totalCurrentBytes, totalLengthBytes) + "  (" + inputFile.Name + ": " + Preset.FormatProcessedByte(fileMovedBytes, fileLengthBytes) + ")";
                        this.CallProgress(progressRatio, progressMessage);
                    }
                }

                result = new System.IO.FileInfo(targetFileName);
                result.LastAccessTimeUtc = inputFile.LastAccessTimeUtc;
                result.LastWriteTimeUtc = inputFile.LastWriteTimeUtc;
                result.Attributes = inputFile.Attributes;
                if (result.Length != fileLengthBytes)
                    throw new System.IO.InvalidDataException("Chyba: zapsaný soubor " + targetFileName + " má velikost (" + result.Length.ToString() + " B) neodpovídající velikosti vstupního souboru " + inputFile.FullName + " (" + fileLengthBytes.ToString() + " B).");
            }
            catch (Exception exc)
            {
                this.CallError(exc);
                result = null;
            }
            finally
            {
                totalMovedBytes += fileLengthBytes;
            }
            return result;
        }
    }
    #endregion
}
